/*
 * Created on 13-Jul-2004
 * Created by Paul Gardner
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

package com.aelitis.azureus.core.impl;

import java.io.File;
import java.net.InetAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.config.impl.TransferSpeedValidator;
import org.gudy.azureus2.core3.disk.DiskManager;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.global.GlobalManager;
import org.gudy.azureus2.core3.global.GlobalManagerAdapter;
import org.gudy.azureus2.core3.global.GlobalManagerFactory;
import org.gudy.azureus2.core3.global.GlobalManagerStats;
import org.gudy.azureus2.core3.global.GlobalMangerProgressListener;
import org.gudy.azureus2.core3.internat.LocaleUtil;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.ipfilter.IpFilterManager;
import org.gudy.azureus2.core3.ipfilter.IpFilterManagerFactory;
import org.gudy.azureus2.core3.logging.LogAlert;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.peer.PEPeerManager;
import org.gudy.azureus2.core3.peer.PEPeerSource;
import org.gudy.azureus2.core3.security.SESecurityManager;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerResponse;
import org.gudy.azureus2.core3.tracker.host.TRHost;
import org.gudy.azureus2.core3.tracker.host.TRHostFactory;
import org.gudy.azureus2.core3.util.AEDiagnostics;
import org.gudy.azureus2.core3.util.AEMonitor;
import org.gudy.azureus2.core3.util.AERunnable;
import org.gudy.azureus2.core3.util.AESemaphore;
import org.gudy.azureus2.core3.util.AETemporaryFileHandler;
import org.gudy.azureus2.core3.util.AEThread;
import org.gudy.azureus2.core3.util.AEThread2;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.DelayedEvent;
import org.gudy.azureus2.core3.util.ListenerManager;
import org.gudy.azureus2.core3.util.ListenerManagerDispatcher;
import org.gudy.azureus2.core3.util.NonDaemonTaskRunner;
import org.gudy.azureus2.core3.util.SHA1Simple;
import org.gudy.azureus2.core3.util.SimpleTimer;
import org.gudy.azureus2.core3.util.SystemProperties;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.core3.util.ThreadPool;
import org.gudy.azureus2.core3.util.TimerEvent;
import org.gudy.azureus2.core3.util.TimerEventPerformer;
import org.gudy.azureus2.core3.util.TimerEventPeriodic;
import org.gudy.azureus2.platform.PlatformManager;
import org.gudy.azureus2.platform.PlatformManagerCapabilities;
import org.gudy.azureus2.platform.PlatformManagerFactory;
import org.gudy.azureus2.platform.PlatformManagerListener;
import org.gudy.azureus2.plugins.PluginEvent;
import org.gudy.azureus2.plugins.PluginEventListener;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.PluginManager;
import org.gudy.azureus2.plugins.PluginManagerDefaults;
import org.gudy.azureus2.plugins.torrent.Torrent;
import org.gudy.azureus2.plugins.torrent.TorrentDownloader;
import org.gudy.azureus2.plugins.utils.DelayedTask;
import org.gudy.azureus2.plugins.utils.PowerManagementListener;
import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils;
import org.gudy.azureus2.pluginsimpl.local.PluginInitializer;
import org.gudy.azureus2.pluginsimpl.local.download.DownloadManagerImpl;
import org.gudy.azureus2.pluginsimpl.local.utils.UtilitiesImpl;

import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreComponent;
import com.aelitis.azureus.core.AzureusCoreException;
import com.aelitis.azureus.core.AzureusCoreLifecycleAdapter;
import com.aelitis.azureus.core.AzureusCoreLifecycleListener;
import com.aelitis.azureus.core.AzureusCoreListener;
import com.aelitis.azureus.core.AzureusCoreOperation;
import com.aelitis.azureus.core.AzureusCoreOperationListener;
import com.aelitis.azureus.core.AzureusCoreOperationTask;
import com.aelitis.azureus.core.AzureusCoreRunningListener;
import com.aelitis.azureus.core.custom.CustomizationManagerFactory;
import com.aelitis.azureus.core.dht.DHT;
import com.aelitis.azureus.core.dht.DHTListener;
import com.aelitis.azureus.core.dht.speed.DHTSpeedTester;
import com.aelitis.azureus.core.instancemanager.AZInstanceManager;
import com.aelitis.azureus.core.instancemanager.AZInstanceManagerAdapter;
import com.aelitis.azureus.core.instancemanager.AZInstanceManagerFactory;
import com.aelitis.azureus.core.instancemanager.AZInstanceTracked;
import com.aelitis.azureus.core.nat.NATTraverser;
import com.aelitis.azureus.core.networkmanager.NetworkManager;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdmin;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminNetworkInterface;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminNetworkInterfaceAddress;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminPropertyChangeListener;
import com.aelitis.azureus.core.networkmanager.impl.tcp.TCPNetworkManager;
import com.aelitis.azureus.core.networkmanager.impl.udp.UDPNetworkManager;
import com.aelitis.azureus.core.pairing.PairingManagerFactory;
import com.aelitis.azureus.core.peermanager.PeerManager;
import com.aelitis.azureus.core.peermanager.nat.PeerNATTraverser;
import com.aelitis.azureus.core.proxy.AEProxySelectorFactory;
import com.aelitis.azureus.core.security.CryptoManager;
import com.aelitis.azureus.core.security.CryptoManagerFactory;
import com.aelitis.azureus.core.speedmanager.SpeedLimitHandler;
import com.aelitis.azureus.core.speedmanager.SpeedManager;
import com.aelitis.azureus.core.speedmanager.SpeedManagerAdapter;
import com.aelitis.azureus.core.speedmanager.SpeedManagerFactory;
import com.aelitis.azureus.core.update.AzureusRestarterFactory;
import com.aelitis.azureus.core.util.CopyOnWriteList;
import com.aelitis.azureus.core.versioncheck.VersionCheckClient;
import com.aelitis.azureus.core.vuzefile.VuzeFile;
import com.aelitis.azureus.core.vuzefile.VuzeFileComponent;
import com.aelitis.azureus.core.vuzefile.VuzeFileHandler;
import com.aelitis.azureus.core.vuzefile.VuzeFileProcessor;
import com.aelitis.azureus.launcher.classloading.PrimaryClassloader;
import com.aelitis.azureus.plugins.clientid.ClientIDPlugin;
import com.aelitis.azureus.plugins.dht.DHTPlugin;
import com.aelitis.azureus.plugins.startstoprules.defaultplugin.DefaultRankCalculator;
import com.aelitis.azureus.plugins.startstoprules.defaultplugin.StartStopRulesDefaultPlugin;
import com.aelitis.azureus.plugins.tracker.dht.DHTTrackerPlugin;
import com.aelitis.azureus.plugins.upnp.UPnPPlugin;
import com.aelitis.azureus.ui.UIFunctions;
import com.aelitis.azureus.ui.UIFunctionsManager;
import com.aelitis.azureus.util.MapUtils;

/**
 * @author parg
 * 
 */

public class AzureusCoreImpl implements AzureusCore {
    private final static LogIDs LOGID = LogIDs.CORE;
    protected static AzureusCore singleton;
    protected static AEMonitor class_mon = new AEMonitor("AzureusCore:class");

    private static final String DM_ANNOUNCE_KEY = "AzureusCore:announce_key";
    private static final boolean LOAD_PLUGINS_IN_OTHER_THREAD = true;

    /**
     * Listeners that will be fired after core has completed initialization
     */
    static List<AzureusCoreRunningListener> coreRunningListeners = new ArrayList<AzureusCoreRunningListener>(1);

    static AEMonitor mon_coreRunningListeners = new AEMonitor("CoreCreationListeners");

    public static AzureusCore create()

    throws AzureusCoreException {
        try {
            class_mon.enter();

            if (singleton != null) {

                throw (new AzureusCoreException("Azureus core already instantiated"));
            }

            singleton = new AzureusCoreImpl();

            return (singleton);

        } finally {

            class_mon.exit();
        }
    }

    public static boolean isCoreAvailable() {
        return (singleton != null);
    }

    public static boolean isCoreRunning() {
        return (singleton != null && singleton.isStarted());
    }

    public static AzureusCore getSingleton()

    throws AzureusCoreException {
        if (singleton == null) {

            throw (new AzureusCoreException("core not instantiated"));
        }

        return (singleton);
    }

    private PluginInitializer pi;
    private GlobalManager global_manager;
    private AZInstanceManager instance_manager;
    private SpeedManager speed_manager;
    private CryptoManager crypto_manager;
    private NATTraverser nat_traverser;

    private final long create_time;

    private volatile boolean started;
    private volatile boolean stopped;
    private volatile boolean restarting;

    private final CopyOnWriteList listeners = new CopyOnWriteList();
    private final CopyOnWriteList lifecycle_listeners = new CopyOnWriteList();
    private final List operation_listeners = new ArrayList();

    private final CopyOnWriteList<PowerManagementListener> power_listeners = new CopyOnWriteList<PowerManagementListener>();

    private final AESemaphore stopping_sem = new AESemaphore("AzureusCore::stopping");

    private final AEMonitor this_mon = new AEMonitor("AzureusCore");

    private final AzureusCoreOperation initialisation_op = createOperation(AzureusCoreOperation.OP_INITIALISATION);

    public static boolean SUPPRESS_CLASSLOADER_ERRORS = false;

    private boolean ca_shutdown_computer_after_stop = false;
    private long ca_last_time_downloading = -1;
    private long ca_last_time_seeding = -1;

    private boolean prevent_sleep_remove_trigger = false;

    protected AzureusCoreImpl() {
        create_time = SystemTime.getCurrentTime();

        // OSTI: Disabled to avoid warning.
        // if (!SUPPRESS_CLASSLOADER_ERRORS && !(this.getClass().getClassLoader() instanceof PrimaryClassloader))
        // System.out
        // .println("###\nWarning: Core not instantiated through a PrimaryClassloader, this can lead to restricted functionality or bugs in future versions\n###");

        COConfigurationManager.initialise();

        MessageText.loadBundle();

        AEDiagnostics.startup(COConfigurationManager.getBooleanParameter("diags.enable.pending.writes", false));

        COConfigurationManager.setParameter("diags.enable.pending.writes", false);

        AEDiagnostics.markDirty();

        AETemporaryFileHandler.startup();

        AEThread2.setOurThread();

        // set up a backwards pointer from config -> app dir so we can derive one from the other. It'll get saved on
        // closedown, no need to do so now

        COConfigurationManager.setParameter("azureus.application.directory", new File(SystemProperties.getApplicationPath()).getAbsolutePath());
        COConfigurationManager.setParameter("azureus.user.directory", new File(SystemProperties.getUserPath()).getAbsolutePath());

        crypto_manager = CryptoManagerFactory.getSingleton();

        PlatformManagerFactory.getPlatformManager().addListener(new PlatformManagerListener() {
            @Override
            public int eventOccurred(int type) {
                if (type == ET_SHUTDOWN) {

                    if (Logger.isEnabled()) {
                        Logger.log(new LogEvent(LOGID, "Platform manager requested shutdown"));
                    }

                    COConfigurationManager.save();

                    requestStop();

                    return (0);

                } else if (type == ET_SUSPEND) {

                    if (Logger.isEnabled()) {
                        Logger.log(new LogEvent(LOGID, "Platform manager requested suspend"));
                    }

                    COConfigurationManager.save();

                } else if (type == ET_RESUME) {

                    if (Logger.isEnabled()) {
                        Logger.log(new LogEvent(LOGID, "Platform manager requested resume"));
                    }

                    announceAll(true);
                }

                return (-1);
            }
        });

        // ensure early initialization

        CustomizationManagerFactory.getSingleton().initialize();

        AEProxySelectorFactory.getSelector();

        NetworkManager.getSingleton();

        PeerManager.getSingleton();

        // Used to be a plugin, but not any more...

        ClientIDPlugin.initialize();

        pi = PluginInitializer.getSingleton(this, initialisation_op);

        instance_manager = AZInstanceManagerFactory.getSingleton(new AZInstanceManagerAdapter() {
            @Override
            public String getID() {
                return (COConfigurationManager.getStringParameter("ID", ""));
            }

            @Override
            public InetAddress getPublicAddress() {
                return (PluginInitializer.getDefaultInterface().getUtilities().getPublicAddress());
            }

            @Override
            public int[] getPorts() {
                return (new int[] { TCPNetworkManager.getSingleton().getTCPListeningPortNumber(),
                        UDPNetworkManager.getSingleton().getUDPListeningPortNumber(),
                        UDPNetworkManager.getSingleton().getUDPNonDataListeningPortNumber() });

            }

            @Override
            public VCPublicAddress getVCPublicAddress() {
                return (new VCPublicAddress() {
                    private final VersionCheckClient vcc = VersionCheckClient.getSingleton();

                    @Override
                    public String getAddress() {
                        return (vcc.getExternalIpAddress(true, false));
                    }

                    @Override
                    public long getCacheTime() {
                        return (vcc.getSingleton().getCacheTime(false));
                    }
                });
            }

            @Override
            public AZInstanceTracked.TrackTarget track(byte[] hash) {
                List dms = getGlobalManager().getDownloadManagers();

                Iterator it = dms.iterator();

                DownloadManager matching_dm = null;

                try {
                    while (it.hasNext()) {

                        DownloadManager dm = (DownloadManager) it.next();

                        TOTorrent torrent = dm.getTorrent();

                        if (torrent == null) {

                            continue;
                        }

                        byte[] sha1_hash = (byte[]) dm.getData("AZInstanceManager::sha1_hash");

                        if (sha1_hash == null) {

                            sha1_hash = new SHA1Simple().calculateHash(torrent.getHash());

                            dm.setData("AZInstanceManager::sha1_hash", sha1_hash);
                        }

                        if (Arrays.equals(hash, sha1_hash)) {

                            matching_dm = dm;

                            break;
                        }
                    }
                } catch (Throwable e) {

                    Debug.printStackTrace(e);
                }

                if (matching_dm == null) {

                    return (null);
                }

                if (!matching_dm.getDownloadState().isPeerSourceEnabled(PEPeerSource.PS_PLUGIN)) {

                    return (null);
                }

                int dm_state = matching_dm.getState();

                if (dm_state == DownloadManager.STATE_ERROR || dm_state == DownloadManager.STATE_STOPPED) {

                    return (null);
                }

                try {

                    final Object target = DownloadManagerImpl.getDownloadStatic(matching_dm);

                    final boolean is_seed = matching_dm.isDownloadComplete(true);

                    return (new AZInstanceTracked.TrackTarget() {
                        @Override
                        public Object getTarget() {
                            return (target);
                        }

                        @Override
                        public boolean isSeed() {
                            return (is_seed);
                        }
                    });

                } catch (Throwable e) {

                    return (null);
                }
            }

            @Override
            public DHTPlugin getDHTPlugin() {
                PluginInterface pi = getPluginManager().getPluginInterfaceByClass(DHTPlugin.class);

                if (pi != null) {

                    return ((DHTPlugin) pi.getPlugin());
                }

                return (null);
            }

            @Override
            public UPnPPlugin getUPnPPlugin() {
                PluginInterface pi = getPluginManager().getPluginInterfaceByClass(UPnPPlugin.class);

                if (pi != null) {

                    return ((UPnPPlugin) pi.getPlugin());
                }

                return (null);
            }

            @Override
            public void addListener(final StateListener listener) {
                AzureusCoreImpl.this.addLifecycleListener(new AzureusCoreLifecycleAdapter() {
                    @Override
                    public void started(AzureusCore core) {
                        listener.started();
                    }

                    @Override
                    public void stopping(AzureusCore core) {
                        listener.stopped();
                    }
                });
            }
        });

        if (COConfigurationManager.getBooleanParameter("speedmanager.enable", true)) {

            speed_manager = SpeedManagerFactory.createSpeedManager(this, new SpeedManagerAdapter() {
                private static final int UPLOAD_SPEED_ADJUST_MIN_KB_SEC = 10;
                private static final int DOWNLOAD_SPEED_ADJUST_MIN_KB_SEC = 300;

                private boolean setting_limits;

                @Override
                public int getCurrentProtocolUploadSpeed(int average_period) {
                    if (global_manager != null) {

                        GlobalManagerStats stats = global_manager.getStats();

                        return (stats.getProtocolSendRateNoLAN(average_period));

                    } else {

                        return (0);
                    }
                }

                @Override
                public int getCurrentDataUploadSpeed(int average_period) {
                    if (global_manager != null) {

                        GlobalManagerStats stats = global_manager.getStats();

                        return (stats.getDataSendRateNoLAN(average_period));

                    } else {

                        return (0);
                    }
                }

                @Override
                public int getCurrentProtocolDownloadSpeed(int average_period) {
                    if (global_manager != null) {
                        GlobalManagerStats stats = global_manager.getStats();
                        return (stats.getProtocolReceiveRateNoLAN(average_period));
                    } else {
                        return (0);
                    }
                }

                @Override
                public int getCurrentDataDownloadSpeed(int average_period) {
                    if (global_manager != null) {
                        GlobalManagerStats stats = global_manager.getStats();
                        return (stats.getDataReceiveRateNoLAN(average_period));
                    } else {
                        return (0);
                    }
                }

                @Override
                public int getCurrentUploadLimit() {
                    String key = TransferSpeedValidator.getActiveUploadParameter(global_manager);

                    int k_per_second = COConfigurationManager.getIntParameter(key);

                    int bytes_per_second;

                    if (k_per_second == 0) {

                        bytes_per_second = Integer.MAX_VALUE;

                    } else {

                        bytes_per_second = k_per_second * 1024;
                    }

                    return (bytes_per_second);
                }

                @Override
                public void setCurrentUploadLimit(int bytes_per_second) {
                    if (bytes_per_second != getCurrentUploadLimit()) {

                        String key = TransferSpeedValidator.getActiveUploadParameter(global_manager);

                        int k_per_second;

                        if (bytes_per_second == Integer.MAX_VALUE) {

                            k_per_second = 0;

                        } else {

                            k_per_second = (bytes_per_second + 1023) / 1024;
                        }

                        if (k_per_second > 0) {

                            k_per_second = Math.max(k_per_second, UPLOAD_SPEED_ADJUST_MIN_KB_SEC);
                        }

                        COConfigurationManager.setParameter(key, k_per_second);
                    }
                }

                @Override
                public int getCurrentDownloadLimit() {
                    return (TransferSpeedValidator.getGlobalDownloadRateLimitBytesPerSecond());
                }

                @Override
                public void setCurrentDownloadLimit(int bytes_per_second) {
                    if (bytes_per_second == Integer.MAX_VALUE) {

                        bytes_per_second = 0;
                    }

                    if (bytes_per_second > 0) {

                        bytes_per_second = Math.max(bytes_per_second, DOWNLOAD_SPEED_ADJUST_MIN_KB_SEC * 1024);
                    }

                    TransferSpeedValidator.setGlobalDownloadRateLimitBytesPerSecond(bytes_per_second);
                }

                @Override
                public Object getLimits() {
                    String up_key = TransferSpeedValidator.getActiveUploadParameter(global_manager);
                    String down_key = TransferSpeedValidator.getDownloadParameter();

                    return (new Object[] { up_key, new Integer(COConfigurationManager.getIntParameter(up_key)), down_key,
                            new Integer(COConfigurationManager.getIntParameter(down_key)), });
                }

                @Override
                public void setLimits(Object limits, boolean do_up, boolean do_down) {
                    if (limits == null) {

                        return;
                    }
                    try {
                        if (setting_limits) {

                            return;
                        }

                        setting_limits = true;

                        Object[] bits = (Object[]) limits;

                        if (do_up) {

                            COConfigurationManager.setParameter((String) bits[0], ((Integer) bits[1]).intValue());
                        }

                        if (do_down) {

                            COConfigurationManager.setParameter((String) bits[2], ((Integer) bits[3]).intValue());
                        }

                    } finally {

                        setting_limits = false;

                    }
                }
            });
        }

        nat_traverser = new NATTraverser(this);

        PeerNATTraverser.initialise(this);

        // one off explicit GC to clear up initialisation mem

        SimpleTimer.addEvent("AzureusCore:gc", SystemTime.getOffsetTime(60 * 1000), new TimerEventPerformer() {
            @Override
            public void perform(TimerEvent event) {
                System.gc();
            }
        });
    }

    @Override
    public long getCreateTime() {
        return (create_time);
    }

    protected void announceAll(boolean force) {
        Logger.log(new LogEvent(LOGID, "Updating trackers"));

        GlobalManager gm = getGlobalManager();

        if (gm != null) {

            List downloads = gm.getDownloadManagers();

            long now = SystemTime.getCurrentTime();

            for (int i = 0; i < downloads.size(); i++) {

                DownloadManager dm = (DownloadManager) downloads.get(i);

                Long last_announce_l = (Long) dm.getUserData(DM_ANNOUNCE_KEY);

                long last_announce = last_announce_l == null ? create_time : last_announce_l.longValue();

                TRTrackerAnnouncer an = dm.getTrackerClient();

                if (an != null) {

                    TRTrackerAnnouncerResponse last_announce_response = an.getLastResponse();

                    if (now - last_announce > 15 * 60 * 1000 || last_announce_response == null
                            || last_announce_response.getStatus() == TRTrackerAnnouncerResponse.ST_OFFLINE || force) {

                        dm.setUserData(DM_ANNOUNCE_KEY, new Long(now));

                        Logger.log(new LogEvent(LOGID, "    updating tracker for " + dm.getDisplayName()));

                        dm.requestTrackerAnnounce(true);
                    }
                }
            }
        }

        PluginInterface dht_tracker_pi = getPluginManager().getPluginInterfaceByClass(DHTTrackerPlugin.class);

        if (dht_tracker_pi != null) {

            ((DHTTrackerPlugin) dht_tracker_pi.getPlugin()).announceAll();
        }
    }

    @Override
    public LocaleUtil getLocaleUtil() {
        return (LocaleUtil.getSingleton());
    }

    @Override
    public void start()

    throws AzureusCoreException {
        AEThread2.setOurThread();

        try {
            this_mon.enter();

            if (started) {

                throw (new AzureusCoreException("Core: already started"));
            }

            if (stopped) {

                throw (new AzureusCoreException("Core: already stopped"));
            }

            started = true;

        } finally {

            this_mon.exit();
        }

        // If a user sets this property, it is an alias for the following settings.
        if ("1".equals(System.getProperty("azureus.safemode"))) {
            if (Logger.isEnabled())
                Logger.log(new LogEvent(LOGID, "Safe mode enabled"));

            Constants.isSafeMode = true;
            System.setProperty("azureus.loadplugins", "0");
            System.setProperty("azureus.disabledownloads", "1");
            System.setProperty("azureus.skipSWTcheck", "1");

            // Not using localised text - not sure it's safe to this early.
            Logger.log(new LogAlert(LogAlert.UNREPEATABLE, LogEvent.LT_WARNING, "You are running " + Constants.APP_NAME + " in safe mode - you "
                    + "can change your configuration, but any downloads added will " + "not be remembered when you close " + Constants.APP_NAME
                    + "."));
        }

        /**
         * test to see if UI plays nicely with a really slow initialization
         */
        String sDelayCore = System.getProperty("delay.core", null);
        if (sDelayCore != null) {
            try {
                long delayCore = Long.parseLong(sDelayCore);
                Thread.sleep(delayCore);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // run plugin loading in parallel to the global manager loading
        AEThread2 pluginload = new AEThread2("PluginLoader", true) {
            @Override
            public void run() {
                // OSTI: Disabled to avoid NoClassDefFoundError of
                // 'org/gudy/azureus2/ui/swt/UIExitUtilsSWT$canCloseListener'
                // if (Logger.isEnabled())
                // Logger.log(new LogEvent(LOGID, "Loading of Plugins starts"));
                // pi.loadPlugins(AzureusCoreImpl.this, false, !"0".equals(System.getProperty("azureus.loadplugins")),
                // true, true);
                // if (Logger.isEnabled())
                // Logger.log(new LogEvent(LOGID, "Loading of Plugins complete"));
            }
        };

        if (LOAD_PLUGINS_IN_OTHER_THREAD) {
            pluginload.start();
        } else {
            pluginload.run();
        }

        // Disable async loading of existing torrents, because there are many things
        // (like hosting) that require all the torrents to be loaded. While we
        // can write code for each of these cases to wait until the torrents are
        // loaded, it's a pretty big job to find them all and fix all their quirks.
        // Too big of a job for this late in the release stage.
        // Other example is the tracker plugin that is coded in a way where it must
        // always publish a complete rss feed

        global_manager = GlobalManagerFactory.create(this, new GlobalMangerProgressListener() {
            @Override
            public void reportCurrentTask(String currentTask) {
                initialisation_op.reportCurrentTask(currentTask);
            }

            @Override
            public void reportPercent(int percent) {
                initialisation_op.reportPercent(percent);
            }
        }, 0);

        if (stopped) {
            System.err.println("Core stopped while starting");
            return;
        }

        // wait until plugin loading is done
        if (LOAD_PLUGINS_IN_OTHER_THREAD) {
            pluginload.join();
        }

        if (stopped) {
            System.err.println("Core stopped while starting");
            return;
        }

        VuzeFileHandler.getSingleton().addProcessor(new VuzeFileProcessor() {
            @Override
            public void process(VuzeFile[] files, int expected_types) {
                for (int i = 0; i < files.length; i++) {

                    VuzeFile vf = files[i];

                    VuzeFileComponent[] comps = vf.getComponents();

                    for (int j = 0; j < comps.length; j++) {

                        VuzeFileComponent comp = comps[j];

                        int comp_type = comp.getType();

                        if (comp_type == VuzeFileComponent.COMP_TYPE_ADD_TORRENT) {

                            PluginInterface default_pi = getPluginManager().getDefaultPluginInterface();

                            Map map = comp.getContent();

                            try {
                                Torrent torrent;

                                String url = MapUtils.getMapString(map, "torrent_url", null);

                                if (url != null) {

                                    TorrentDownloader dl = default_pi.getTorrentManager().getURLDownloader(new URL(url));

                                    torrent = dl.download();

                                } else {

                                    String tf = MapUtils.getMapString(map, "torrent_file", null);

                                    if (tf != null) {

                                        File file = new File(tf);

                                        if (!file.canRead() || file.isDirectory()) {

                                            throw (new Exception("torrent_file '" + tf + "' is invalid"));
                                        }

                                        torrent = default_pi.getTorrentManager().createFromBEncodedFile(file);

                                    } else {

                                        throw (new Exception("torrent_url or torrent_file must be specified"));
                                    }
                                }

                                File dest = null;

                                String save_folder = MapUtils.getMapString(map, "save_folder", null);

                                if (save_folder != null) {

                                    dest = new File(save_folder, torrent.getName());

                                } else {

                                    String save_file = MapUtils.getMapString(map, "save_file", null);

                                    if (save_file != null) {

                                        dest = new File(save_file);
                                    }
                                }

                                if (dest != null) {

                                    dest.getParentFile().mkdirs();
                                }

                                default_pi.getDownloadManager().addDownload(torrent, null, dest);

                            } catch (Throwable e) {

                                Debug.out(e);
                            }

                            comp.setProcessed();
                        }
                    }
                }
            }
        });

        triggerLifeCycleComponentCreated(global_manager);

        pi.initialisePlugins();

        if (stopped) {
            System.err.println("Core stopped while starting");
            return;
        }

        if (Logger.isEnabled())
            Logger.log(new LogEvent(LOGID, "Initializing Plugins complete"));

        try {
            PluginInterface dht_pi = getPluginManager().getPluginInterfaceByClass(DHTPlugin.class);

            if (dht_pi != null) {

                dht_pi.addEventListener(new PluginEventListener() {
                    private boolean first_dht = true;

                    @Override
                    public void handleEvent(PluginEvent ev) {
                        if (ev.getType() == DHTPlugin.EVENT_DHT_AVAILABLE) {

                            if (first_dht) {

                                first_dht = false;

                                DHT dht = (DHT) ev.getValue();

                                dht.addListener(new DHTListener() {
                                    @Override
                                    public void speedTesterAvailable(DHTSpeedTester tester) {
                                        if (speed_manager != null) {

                                            speed_manager.setSpeedTester(tester);
                                        }
                                    }
                                });

                                global_manager.addListener(new GlobalManagerAdapter() {
                                    @Override
                                    public void seedingStatusChanged(boolean seeding_only_mode, boolean b) {
                                        checkConfig();
                                    }
                                });

                                COConfigurationManager.addAndFireParameterListeners(new String[] {
                                        TransferSpeedValidator.AUTO_UPLOAD_ENABLED_CONFIGKEY,
                                        TransferSpeedValidator.AUTO_UPLOAD_SEEDING_ENABLED_CONFIGKEY }, new ParameterListener() {
                                    @Override
                                    public void parameterChanged(String parameterName) {
                                        checkConfig();
                                    }
                                });

                            }
                        }
                    }

                    protected void checkConfig() {
                        if (speed_manager != null) {

                            speed_manager.setEnabled(TransferSpeedValidator.isAutoSpeedActive(global_manager));
                        }
                    }

                });
            }
        } catch (Throwable e) {
        }

        if (COConfigurationManager.getBooleanParameter("Resume Downloads On Start")) {

            global_manager.resumeDownloads();
        }

        // OSTI: Disabled version check.
        // VersionCheckClient.getSingleton().initialise();

        instance_manager.initialize();

        NetworkManager.getSingleton().initialize(this);

        SpeedLimitHandler.getSingleton(this);

        Runtime.getRuntime().addShutdownHook(new AEThread("Shutdown Hook") {
            @Override
            public void runSupport() {
                Logger.log(new LogEvent(LOGID, "Shutdown hook triggered"));
                AzureusCoreImpl.this.stop();
            }
        });

        DelayedTask delayed_task = UtilitiesImpl.addDelayedTask("Core", new Runnable() {
            @Override
            public void run() {
                new AEThread2("core:delayTask", true) {
                    @Override
                    public void run() {
                        AEDiagnostics.checkDumpsAndNatives();

                        COConfigurationManager.setParameter("diags.enable.pending.writes", true);

                        AEDiagnostics.flushPendingLogs();

                        NetworkAdmin na = NetworkAdmin.getSingleton();

                        na.runInitialChecks(AzureusCoreImpl.this);

                        na.addPropertyChangeListener(new NetworkAdminPropertyChangeListener() {
                            private String last_as;

                            @Override
                            public void propertyChanged(String property) {
                                NetworkAdmin na = NetworkAdmin.getSingleton();

                                if (property.equals(NetworkAdmin.PR_NETWORK_INTERFACES)) {

                                    boolean found_usable = false;

                                    NetworkAdminNetworkInterface[] intf = na.getInterfaces();

                                    for (int i = 0; i < intf.length; i++) {

                                        NetworkAdminNetworkInterfaceAddress[] addresses = intf[i].getAddresses();

                                        for (int j = 0; j < addresses.length; j++) {

                                            if (!addresses[j].isLoopback()) {

                                                found_usable = true;
                                            }
                                        }
                                    }

                                    // ignore event if nothing usable

                                    if (!found_usable) {

                                        return;
                                    }

                                    Logger.log(new LogEvent(LOGID, "Network interfaces have changed (new=" + na.getNetworkInterfacesAsString()
                                            + ")"));

                                    announceAll(false);

                                } else if (property.equals(NetworkAdmin.PR_AS)) {

                                    String as = na.getCurrentASN().getAS();

                                    if (last_as == null) {

                                        last_as = as;

                                    } else if (!as.equals(last_as)) {

                                        Logger.log(new LogEvent(LOGID, "AS has changed (new=" + as + ")"));

                                        last_as = as;

                                        announceAll(false);
                                    }
                                }
                            }
                        });

                        setupSleepAndCloseActions();
                    }
                }.start();
            }
        });

        delayed_task.queue();

        if (stopped) {
            System.err.println("Core stopped while starting");
            return;
        }

        PairingManagerFactory.getSingleton();

        AzureusCoreRunningListener[] runningListeners;
        mon_coreRunningListeners.enter();
        try {
            if (coreRunningListeners == null) {
                runningListeners = new AzureusCoreRunningListener[0];
            } else {
                runningListeners = coreRunningListeners.toArray(new AzureusCoreRunningListener[0]);
                coreRunningListeners = null;
            }

        } finally {
            mon_coreRunningListeners.exit();
        }

        // Trigger Listeners now that core is started
        new AEThread2("Plugin Init Complete", false) {
            @Override
            public void run() {
                try {
                    PlatformManagerFactory.getPlatformManager().startup(AzureusCoreImpl.this);

                } catch (Throwable e) {

                    Debug.out("PlatformManager: init failed", e);
                }

                Iterator it = lifecycle_listeners.iterator();

                while (it.hasNext()) {

                    try {
                        AzureusCoreLifecycleListener listener = (AzureusCoreLifecycleListener) it.next();

                        if (!listener.requiresPluginInitCompleteBeforeStartedEvent()) {

                            listener.started(AzureusCoreImpl.this);
                        }
                    } catch (Throwable e) {

                        Debug.printStackTrace(e);
                    }
                }

                pi.initialisationComplete();

                it = lifecycle_listeners.iterator();

                while (it.hasNext()) {

                    try {
                        AzureusCoreLifecycleListener listener = (AzureusCoreLifecycleListener) it.next();

                        if (listener.requiresPluginInitCompleteBeforeStartedEvent()) {

                            listener.started(AzureusCoreImpl.this);
                        }
                    } catch (Throwable e) {

                        Debug.printStackTrace(e);
                    }
                }
            }
        }.start();

        // Typicially there are many runningListeners, most with quick execution, and
        // a few longer ones. Let 3 run at a time, queue the rest. Without
        // a ThreadPool, the slow ones would delay the startup processes that run
        // after this start() method
        ThreadPool tp = new ThreadPool("Trigger AzureusCoreRunning Listeners", 3);
        for (final AzureusCoreRunningListener l : runningListeners) {
            try {
                tp.run(new AERunnable() {
                    @Override
                    public void runSupport() {
                        l.azureusCoreRunning(AzureusCoreImpl.this);
                    }
                });
            } catch (Throwable t) {
                Debug.out(t);
            }
        }

        // Debug.out("Core Start Complete");
    }

    @Override
    public boolean isInitThread() {
        return (AEThread2.isOurThread(Thread.currentThread()));
    }

    @Override
    public boolean isStarted() {
        mon_coreRunningListeners.enter();
        try {
            return (started && coreRunningListeners == null);
        } finally {
            mon_coreRunningListeners.exit();
        }
    }

    @Override
    public void triggerLifeCycleComponentCreated(AzureusCoreComponent component) {
        Iterator it = lifecycle_listeners.iterator();

        while (it.hasNext()) {

            try {
                ((AzureusCoreLifecycleListener) it.next()).componentCreated(this, component);

            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }
    }

    private void runNonDaemon(final Runnable r)

    throws AzureusCoreException {
        if (!Thread.currentThread().isDaemon()) {

            r.run();

        } else {

            final AESemaphore sem = new AESemaphore("AzureusCore:runNonDaemon");

            final Throwable[] error = { null };

            new AEThread2("AzureusCore:runNonDaemon", false) {
                @Override
                public void run() {
                    try {

                        r.run();

                    } catch (Throwable e) {

                        error[0] = e;

                    } finally {

                        sem.release();
                    }
                }
            }.start();

            sem.reserve();

            if (error[0] != null) {

                if (error[0] instanceof AzureusCoreException) {

                    throw ((AzureusCoreException) error[0]);

                } else {

                    throw (new AzureusCoreException("Operation failed", error[0]));
                }
            }
        }
    }

    @Override
    public void stop()

    throws AzureusCoreException {
        runNonDaemon(new AERunnable() {
            @Override
            public void runSupport() {
                if (Logger.isEnabled())
                    Logger.log(new LogEvent(LOGID, "Stop operation starts"));

                stopSupport(true);
            }
        });
    }

    private void stopSupport(boolean apply_updates)

    throws AzureusCoreException {
        AEDiagnostics.flushPendingLogs();

        boolean wait_and_return = false;

        try {
            this_mon.enter();

            if (stopped) {

                // ensure config is saved as there may be pending changes to persist and we've got here
                // via a shutdown hook

                COConfigurationManager.save();

                wait_and_return = true;

            } else {

                stopped = true;

                if (!started) {

                    Logger.log(new LogEvent(LOGID, "Core not started"));

                    // might have been marked dirty due to core being created to allow functions to be used but never
                    // started...

                    if (AEDiagnostics.isDirty()) {

                        AEDiagnostics.markClean();
                    }

                    stopping_sem.releaseForever();

                    return;
                }
            }
        } finally {

            this_mon.exit();
        }

        if (wait_and_return) {

            Logger.log(new LogEvent(LOGID, "Waiting for stop to complete"));

            stopping_sem.reserve();

            return;
        }

        SimpleTimer.addEvent("ShutFail", SystemTime.getOffsetTime(30 * 1000), new TimerEventPerformer() {
            boolean die_die_die;

            @Override
            public void perform(TimerEvent event) {
                AEDiagnostics.dumpThreads();

                if (die_die_die) {

                    Debug.out("Shutdown blocked, force exiting");

                    stopping_sem.releaseForever();

                    SESecurityManager.exitVM(0);
                }

                die_die_die = true;

                SimpleTimer.addEvent("ShutFail", SystemTime.getOffsetTime(30 * 1000), this);
            }
        });

        List sync_listeners = new ArrayList();
        List async_listeners = new ArrayList();

        Iterator it = lifecycle_listeners.iterator();

        while (it.hasNext()) {
            AzureusCoreLifecycleListener l = (AzureusCoreLifecycleListener) it.next();

            if (l.syncInvokeRequired()) {
                sync_listeners.add(l);
            } else {
                async_listeners.add(l);
            }
        }

        try {
            if (Logger.isEnabled())
                Logger.log(new LogEvent(LOGID, "Invoking synchronous 'stopping' listeners"));

            for (int i = 0; i < sync_listeners.size(); i++) {
                try {
                    ((AzureusCoreLifecycleListener) sync_listeners.get(i)).stopping(this);

                } catch (Throwable e) {

                    Debug.printStackTrace(e);
                }
            }

            if (Logger.isEnabled())
                Logger.log(new LogEvent(LOGID, "Invoking asynchronous 'stopping' listeners"));

            // in case something hangs during listener notification (e.g. version check server is down
            // and the instance manager tries to obtain external address) we limit overall dispatch
            // time to 10 seconds

            ListenerManager.dispatchWithTimeout(async_listeners, new ListenerManagerDispatcher() {
                @Override
                public void dispatch(Object listener, int type, Object value) {
                    ((AzureusCoreLifecycleListener) listener).stopping(AzureusCoreImpl.this);
                }
            }, 10 * 1000);

            if (Logger.isEnabled())
                Logger.log(new LogEvent(LOGID, "Stopping global manager"));

            if (global_manager != null) {
                global_manager.stopGlobalManager();
            }

            if (Logger.isEnabled())
                Logger.log(new LogEvent(LOGID, "Invoking synchronous 'stopped' listeners"));

            for (int i = 0; i < sync_listeners.size(); i++) {
                try {
                    ((AzureusCoreLifecycleListener) sync_listeners.get(i)).stopped(this);

                } catch (Throwable e) {

                    Debug.printStackTrace(e);
                }
            }

            if (Logger.isEnabled())
                Logger.log(new LogEvent(LOGID, "Invoking asynchronous 'stopped' listeners"));

            ListenerManager.dispatchWithTimeout(async_listeners, new ListenerManagerDispatcher() {
                @Override
                public void dispatch(Object listener, int type, Object value) {
                    ((AzureusCoreLifecycleListener) listener).stopped(AzureusCoreImpl.this);
                }
            }, 10 * 1000);

            if (Logger.isEnabled())
                Logger.log(new LogEvent(LOGID, "Waiting for quiescence"));

            NonDaemonTaskRunner.waitUntilIdle();

            // shut down diags - this marks the shutdown as tidy and saves the config

            AEDiagnostics.markClean();

            if (Logger.isEnabled())
                Logger.log(new LogEvent(LOGID, "Stop operation completes"));

            // if any installers exist then we need to closedown via the updater

            if (apply_updates && getPluginManager().getDefaultPluginInterface().getUpdateManager().getInstallers().length > 0) {

                AzureusRestarterFactory.create(this).restart(true);
            }

            try {
                Class c = Class.forName("sun.awt.AWTAutoShutdown");

                if (c != null) {
                    c.getMethod("notifyToolkitThreadFree", new Class[] {}).invoke(null, new Object[] {});
                }
            } catch (Throwable t) {
            }

            if (ca_shutdown_computer_after_stop) {

                if (apply_updates) {

                    // best we can do here is wait a while for updates to be applied
                    try {
                        Thread.sleep(10 * 1000);

                    } catch (Throwable e) {

                    }
                }

                try {
                    PlatformManagerFactory.getPlatformManager().shutdown(PlatformManager.SD_SHUTDOWN);

                } catch (Throwable e) {

                    Debug.out("PlatformManager: shutdown failed", e);
                }
            }

            try {
                ThreadGroup tg = Thread.currentThread().getThreadGroup();

                while (tg.getParent() != null) {

                    tg = tg.getParent();
                }

                Thread[] threads = new Thread[tg.activeCount() + 1024];

                tg.enumerate(threads, true);

                boolean bad_found = false;

                for (int i = 0; i < threads.length; i++) {

                    Thread t = threads[i];

                    if (t != null && t.isAlive() && t != Thread.currentThread() && !t.isDaemon() && !AEThread2.isOurThread(t)) {

                        bad_found = true;

                        break;
                    }
                }

                if (bad_found) {

                    new AEThread2("VMKiller", true) {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(10 * 1000);

                                ThreadGroup tg = Thread.currentThread().getThreadGroup();

                                Thread[] threads = new Thread[tg.activeCount() + 1024];

                                tg.enumerate(threads, true);

                                String bad_found = "";

                                for (int i = 0; i < threads.length; i++) {

                                    Thread t = threads[i];

                                    if (t != null && t.isAlive() && !t.isDaemon() && !AEThread2.isOurThread(t)) {

                                        String details = t.getName();

                                        StackTraceElement[] trace = t.getStackTrace();

                                        if (trace.length > 0) {

                                            details += "[";

                                            for (int j = 0; j < trace.length; j++) {

                                                details += (j == 0 ? "" : ",") + trace[j];
                                            }

                                            details += "]";
                                        }

                                        bad_found += (bad_found.length() == 0 ? "" : ", ") + details;
                                    }
                                }

                                Debug.out("Non-daemon thread(s) found: '" + bad_found + "' - force closing VM");

                                SESecurityManager.exitVM(0);

                            } catch (Throwable e) {

                            }
                        }
                    }.start();

                }
            } catch (Throwable e) {
            }

        } finally {

            stopping_sem.releaseForever();
        }
    }

    @Override
    public void requestStop()

    throws AzureusCoreException {
        if (stopped)
            return;

        runNonDaemon(new AERunnable() {
            @Override
            public void runSupport() {

                Iterator it = lifecycle_listeners.iterator();

                while (it.hasNext()) {

                    if (!((AzureusCoreLifecycleListener) it.next()).stopRequested(AzureusCoreImpl.this)) {
                        if (Logger.isEnabled())
                            Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING, "Request to stop the core has been denied"));

                        return;
                    }
                }

                stop();
            }
        });
    }

    @Override
    public void restart()

    throws AzureusCoreException {
        runNonDaemon(new AERunnable() {
            @Override
            public void runSupport() {
                if (Logger.isEnabled())
                    Logger.log(new LogEvent(LOGID, "Restart operation starts"));

                checkRestartSupported();

                restarting = true;

                stopSupport(false);

                if (Logger.isEnabled())
                    Logger.log(new LogEvent(LOGID, "Restart operation: stop complete," + "restart initiated"));

                AzureusRestarterFactory.create(AzureusCoreImpl.this).restart(false);
            }
        });
    }

    @Override
    public void requestRestart()

    throws AzureusCoreException {
        runNonDaemon(new AERunnable() {
            @Override
            public void runSupport() {
                checkRestartSupported();

                Iterator it = lifecycle_listeners.iterator();

                while (it.hasNext()) {
                    AzureusCoreLifecycleListener l = (AzureusCoreLifecycleListener) it.next();

                    if (!l.restartRequested(AzureusCoreImpl.this)) {

                        if (Logger.isEnabled())
                            Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING, "Request to restart the core" + " has been denied"));

                        return;
                    }
                }

                restart();
            }
        });
    }

    @Override
    public boolean isRestarting() {
        return (restarting);
    }

    @Override
    public void checkRestartSupported()

    throws AzureusCoreException {
        if (getPluginManager().getPluginInterfaceByClass("org.gudy.azureus2.update.UpdaterPatcher") == null) {
            Logger.log(new LogAlert(LogAlert.REPEATABLE, LogAlert.AT_ERROR, "Can't restart without the 'azupdater' plugin installed"));

            throw (new AzureusCoreException("Can't restart without the 'azupdater' plugin installed"));
        }
    }

    @Override
    public void saveState() {
        GlobalManager gm = global_manager;

        if (gm != null) {

            gm.saveState();
        }

        COConfigurationManager.save();
    }

    @Override
    public GlobalManager getGlobalManager()

    throws AzureusCoreException {
        if (global_manager == null) {

            throw (new AzureusCoreException("Core not running"));
        }

        return (global_manager);
    }

    @Override
    public TRHost getTrackerHost()

    throws AzureusCoreException {
        return (TRHostFactory.getSingleton());
    }

    @Override
    public PluginManagerDefaults getPluginManagerDefaults()

    throws AzureusCoreException {
        return (PluginManager.getDefaults());
    }

    @Override
    public PluginManager getPluginManager()

    throws AzureusCoreException {
        // don't test for running here, the restart process calls this after terminating the core...

        return (PluginInitializer.getDefaultInterface().getPluginManager());
    }

    @Override
    public IpFilterManager getIpFilterManager()

    throws AzureusCoreException {
        return (IpFilterManagerFactory.getSingleton());
    }

    @Override
    public AZInstanceManager getInstanceManager() {
        return (instance_manager);
    }

    @Override
    public SpeedManager getSpeedManager() {
        return (speed_manager);
    }

    @Override
    public CryptoManager getCryptoManager() {
        return (crypto_manager);
    }

    @Override
    public NATTraverser getNATTraverser() {
        return (nat_traverser);
    }

    private void setupSleepAndCloseActions() {
        if (PlatformManagerFactory.getPlatformManager().hasCapability(PlatformManagerCapabilities.PreventComputerSleep)) {

            COConfigurationManager.addAndFireParameterListeners(new String[] { "Prevent Sleep Downloading", "Prevent Sleep FP Seeding", },
                    new ParameterListener() {
                        private TimerEventPeriodic timer_event;

                        @Override
                        public void parameterChanged(String parameterName) {
                            synchronized (this) {

                                boolean dl = COConfigurationManager.getBooleanParameter("Prevent Sleep Downloading");
                                boolean se = COConfigurationManager.getBooleanParameter("Prevent Sleep FP Seeding");

                                boolean active = dl || se;

                                try {
                                    setPreventComputerSleep(PlatformManagerFactory.getPlatformManager(), active, "config change");

                                } catch (Throwable e) {

                                    Debug.out(e);
                                }

                                if (!active) {

                                    if (timer_event != null) {

                                        timer_event.cancel();

                                        timer_event = null;
                                    }
                                } else {

                                    if (timer_event == null) {

                                        timer_event = SimpleTimer.addPeriodicEvent("core:sleepAct", 2 * 60 * 1000, new TimerEventPerformer() {
                                            @Override
                                            public void perform(TimerEvent event) {
                                                if (!stopped) {

                                                    checkSleepActions();
                                                }
                                            }
                                        });
                                    }
                                }
                            }
                        }
                    });
        }

        COConfigurationManager.addAndFireParameterListeners(new String[] { "On Downloading Complete Do", "On Seeding Complete Do" },
                new ParameterListener() {
                    private TimerEventPeriodic timer_event;

                    @Override
                    public void parameterChanged(String parameterName) {
                        String dl_act = COConfigurationManager.getStringParameter("On Downloading Complete Do");
                        String se_act = COConfigurationManager.getStringParameter("On Seeding Complete Do");

                        synchronized (this) {

                            boolean dl_nothing = dl_act.equals("Nothing");
                            boolean se_nothing = se_act.equals("Nothing");

                            if (dl_nothing) {

                                ca_last_time_downloading = -1;
                            }

                            if (se_nothing) {

                                ca_last_time_seeding = -1;
                            }

                            if (dl_nothing && se_nothing) {

                                if (timer_event != null) {

                                    timer_event.cancel();

                                    timer_event = null;
                                }
                            } else {

                                if (timer_event == null) {

                                    timer_event = SimpleTimer.addPeriodicEvent("core:closeAct", 30 * 1000, new TimerEventPerformer() {
                                        @Override
                                        public void perform(TimerEvent event) {
                                            if (!stopped) {

                                                checkCloseActions();
                                            }
                                        }
                                    });
                                }

                                checkCloseActions();
                            }
                        }
                    }
                });
    }

    protected void checkSleepActions() {
        boolean ps_downloading = COConfigurationManager.getBooleanParameter("Prevent Sleep Downloading");
        boolean ps_fp_seed = COConfigurationManager.getBooleanParameter("Prevent Sleep FP Seeding");

        String declining_subsystems = "";

        for (PowerManagementListener l : power_listeners) {

            try {
                if (!l.requestPowerStateChange(PowerManagementListener.ST_SLEEP, null)) {

                    declining_subsystems += (declining_subsystems.length() == 0 ? "" : ",") + l.getPowerName();
                }

            } catch (Throwable e) {

                Debug.out(e);
            }
        }

        if (declining_subsystems.length() == 0 && !(ps_downloading || ps_fp_seed)) {

            return;
        }

        PlatformManager platform = PlatformManagerFactory.getPlatformManager();

        boolean prevent_sleep = false;
        String prevent_reason = null;

        if (declining_subsystems.length() > 0) {

            prevent_sleep = true;
            prevent_reason = "subsystems declined sleep: " + declining_subsystems;

        } else {

            List<DownloadManager> managers = getGlobalManager().getDownloadManagers();

            for (DownloadManager manager : managers) {

                int state = manager.getState();

                if (state == DownloadManager.STATE_FINISHING || manager.getDownloadState().getFlag(DownloadManagerState.FLAG_METADATA_DOWNLOAD)) {

                    if (ps_downloading) {

                        prevent_sleep = true;
                        prevent_reason = "active downloads";

                        break;
                    }

                } else {

                    if (state == DownloadManager.STATE_DOWNLOADING) {

                        PEPeerManager pm = manager.getPeerManager();

                        if (pm != null) {

                            if (pm.hasDownloadablePiece()) {

                                if (ps_downloading) {

                                    prevent_sleep = true;
                                    prevent_reason = "active downloads";

                                    break;
                                }
                            } else {

                                // its effectively seeding, change so logic about recheck obeyed below

                                state = DownloadManager.STATE_SEEDING;
                            }
                        }
                    }

                    if (state == DownloadManager.STATE_SEEDING && ps_fp_seed) {

                        DiskManager disk_manager = manager.getDiskManager();

                        if (disk_manager != null && disk_manager.getCompleteRecheckStatus() != -1) {

                            // wait until recheck is complete before we mark as downloading-complete

                            if (ps_downloading) {

                                prevent_sleep = true;
                                prevent_reason = "active downloads";

                                break;
                            }

                        } else {

                            try {
                                DefaultRankCalculator calc = StartStopRulesDefaultPlugin.getRankCalculator(PluginCoreUtils.wrap(manager));

                                if (calc.getCachedIsFP()) {

                                    prevent_sleep = true;
                                    prevent_reason = "first-priority seeding";

                                    break;
                                }
                            } catch (Throwable e) {

                            }
                        }
                    }
                }
            }
        }

        if (prevent_sleep != platform.getPreventComputerSleep()) {

            if (prevent_sleep) {

                prevent_sleep_remove_trigger = false;

            } else {

                if (!prevent_sleep_remove_trigger) {

                    prevent_sleep_remove_trigger = true;

                    return;
                }
            }

            if (prevent_reason == null) {

                if (ps_downloading && ps_fp_seed) {

                    prevent_reason = "no active downloads or first-priority seeding";

                } else if (ps_downloading) {

                    prevent_reason = "no active downloads";

                } else {

                    prevent_reason = "no active first-priority seeding";
                }
            }

            setPreventComputerSleep(platform, prevent_sleep, prevent_reason);
        }
    }

    private void setPreventComputerSleep(PlatformManager platform, boolean prevent_sleep, String prevent_reason) {
        for (PowerManagementListener l : power_listeners) {

            try {
                l.informPowerStateChange(PowerManagementListener.ST_SLEEP, new Object[] { prevent_sleep, prevent_reason });

            } catch (Throwable e) {

                Debug.out(e);
            }
        }

        Logger.log(new LogEvent(LOGID, "Computer sleep prevented state changed to '" + prevent_sleep + "' due to " + prevent_reason));

        try {
            platform.setPreventComputerSleep(prevent_sleep);

        } catch (Throwable e) {

            Debug.out(e);
        }
    }

    protected void checkCloseActions() {
        List<DownloadManager> managers = getGlobalManager().getDownloadManagers();

        boolean is_downloading = false;
        boolean is_seeding = false;

        for (DownloadManager manager : managers) {

            if (manager.isPaused()) {

                // if anything's paused we don't want to trigger any actions as something transient (e.g. speed test) is
                // going on

                return;
            }

            if (manager.getDownloadState().getFlag(DownloadManagerState.FLAG_METADATA_DOWNLOAD)) {

                // we want this to complete before considering actions

                return;
            }

            if (manager.getDownloadState().getFlag(DownloadManagerState.FLAG_LOW_NOISE)) {

                continue; // don't count these as interesting as the user isn't directly interested in them
            }

            int state = manager.getState();

            if (state == DownloadManager.STATE_FINISHING) {

                is_downloading = true;

            } else {

                if (state == DownloadManager.STATE_DOWNLOADING) {

                    PEPeerManager pm = manager.getPeerManager();

                    if (pm != null) {

                        if (pm.hasDownloadablePiece()) {

                            is_downloading = true;

                        } else {

                            // its effectively seeding, change so logic about recheck obeyed below

                            state = DownloadManager.STATE_SEEDING;
                        }
                    }
                }

                if (state == DownloadManager.STATE_SEEDING) {

                    DiskManager disk_manager = manager.getDiskManager();

                    if (disk_manager != null && disk_manager.getCompleteRecheckStatus() != -1) {

                        // wait until recheck is complete before we mark as downloading-complete

                        is_downloading = true;

                    } else {

                        is_seeding = true;
                    }
                }
            }
        }

        long now = SystemTime.getMonotonousTime();

        if (is_downloading) {

            ca_last_time_downloading = now;
            ca_last_time_seeding = -1;

        } else if (is_seeding) {

            ca_last_time_seeding = now;
        }

        String dl_act = COConfigurationManager.getStringParameter("On Downloading Complete Do");

        if (!dl_act.equals("Nothing")) {

            if (ca_last_time_downloading >= 0 && !is_downloading && now - ca_last_time_downloading >= 30 * 1000) {

                executeCloseAction(true, true, dl_act, null);
            }
        }

        String se_act = COConfigurationManager.getStringParameter("On Seeding Complete Do");

        if (!se_act.equals("Nothing")) {

            if (ca_last_time_seeding >= 0 && !is_seeding && now - ca_last_time_seeding >= 30 * 1000) {

                executeCloseAction(true, false, se_act, null);
            }
        }
    }

    @Override
    public void executeCloseAction(String action, String reason) {
        executeCloseAction(false, false, action, reason);
    }

    private void executeCloseAction(final boolean obey_reset, final boolean download_trigger, final String action, final String reason) {
        // prevent retriggering on resume from standby

        ca_last_time_downloading = -1;
        ca_last_time_seeding = -1;

        boolean reset = obey_reset && COConfigurationManager.getBooleanParameter("Stop Triggers Auto Reset");

        if (reset) {

            if (download_trigger) {

                COConfigurationManager.setParameter("On Downloading Complete Do", "Nothing");

            } else {

                COConfigurationManager.setParameter("On Seeding Complete Do", "Nothing");
            }
        }

        String type_str = reason == null ? MessageText.getString("core.shutdown." + (download_trigger ? "dl" : "se")) : reason;
        String action_str = MessageText.getString("ConfigView.label.stop." + action);

        String message = MessageText.getString("core.shutdown.alert", new String[] { action_str, type_str, });

        UIFunctions ui_functions = UIFunctionsManager.getUIFunctions();

        if (ui_functions != null) {

            ui_functions.forceNotify(UIFunctions.STATUSICON_NONE, null, message, null, new Object[0], -1);
        }

        Logger.log(new LogAlert(LogAlert.UNREPEATABLE, LogEvent.LT_INFORMATION, message));

        new DelayedEvent("CoreShutdown", 10 * 1000, new AERunnable() {
            @Override
            public void runSupport() {
                Logger.log(new LogEvent(LOGID, "Executing close action '" + action + "' due to " + (download_trigger ? "downloading" : "seeding")
                        + " completion"));

                // quit vuze -> quit
                // shutdown computer -> quit vuze + shutdown
                // sleep/hibernate = announceAll and then sleep/hibernate with Vuze still running

                if (action.equals(CA_QUIT_VUZE)) {

                    requestStop();

                } else if (action.equals(CA_SLEEP) || action.equals(CA_HIBERNATE)) {

                    announceAll(true);

                    try {
                        PlatformManagerFactory.getPlatformManager().shutdown(
                                action.equals(CA_SLEEP) ? PlatformManager.SD_SLEEP : PlatformManager.SD_HIBERNATE);

                    } catch (Throwable e) {

                        Debug.out("PlatformManager: shutdown failed", e);
                    }

                } else if (action.equals(CA_SHUTDOWN)) {

                    ca_shutdown_computer_after_stop = true;

                    requestStop();

                } else if (action.startsWith("RunScript")) {

                    String script;

                    if (download_trigger) {

                        script = COConfigurationManager.getStringParameter("On Downloading Complete Script", "");

                    } else {

                        script = COConfigurationManager.getStringParameter("On Seeding Complete Script", "");
                    }

                    File script_file = new File(script.trim());

                    if (!script_file.isFile()) {

                        Logger.log(new LogEvent(LOGID, "Script failed to run - '" + script_file + "' isn't a valid script file"));

                        Debug.out("Invalid script: " + script_file);

                    } else {

                        try {
                            boolean close_vuze = action.equals("RunScriptAndClose");

                            if (!close_vuze) {

                                // assume script might implement a sleep

                                announceAll(true);
                            }

                            getPluginManager().getDefaultPluginInterface().getUtilities().createProcess(script_file.getAbsolutePath());

                            if (close_vuze) {

                                requestStop();
                            }

                        } catch (Throwable e) {

                            Debug.out(e);
                        }
                    }

                } else {

                    Debug.out("Unknown close action '" + action + "'");
                }
            }
        });
    }

    @Override
    public AzureusCoreOperation createOperation(final int type) {
        AzureusCoreOperation op = new AzureusCoreOperation() {
            @Override
            public int getOperationType() {
                return (type);
            }

            @Override
            public AzureusCoreOperationTask getTask() {
                return null;
            }

            @Override
            public void reportCurrentTask(String task) {
                AzureusCoreImpl.this.reportCurrentTask(this, task);
            }

            @Override
            public void reportPercent(int percent) {
                AzureusCoreImpl.this.reportPercent(this, percent);
            }
        };

        for (int i = 0; i < operation_listeners.size(); i++) {

            try {
                ((AzureusCoreOperationListener) operation_listeners.get(i)).operationCreated(op);

            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }

        return (op);
    }

    @Override
    public void createOperation(final int type, AzureusCoreOperationTask task) {
        final AzureusCoreOperationTask[] f_task = { task };

        AzureusCoreOperation op = new AzureusCoreOperation() {
            @Override
            public int getOperationType() {
                return (type);
            }

            @Override
            public AzureusCoreOperationTask getTask() {
                return (f_task[0]);
            }

            @Override
            public void reportCurrentTask(String task) {
                AzureusCoreImpl.this.reportCurrentTask(this, task);
            }

            @Override
            public void reportPercent(int percent) {
                AzureusCoreImpl.this.reportPercent(this, percent);
            }
        };

        for (int i = 0; i < operation_listeners.size(); i++) {

            // don't catch exceptions here as we want errors from task execution to propagate
            // back to the invoker

            if (((AzureusCoreOperationListener) operation_listeners.get(i)).operationCreated(op)) {

                f_task[0] = null;
            }
        }

        // nobody volunteeered to run it for us, we'd better do it

        if (f_task[0] != null) {

            task.run(op);
        }
    }

    protected void reportCurrentTask(AzureusCoreOperation op, String currentTask) {
        if (op.getOperationType() == AzureusCoreOperation.OP_INITIALISATION) {

            PluginInitializer.fireEvent(PluginEvent.PEV_INITIALISATION_PROGRESS_TASK, currentTask);
        }

        Iterator it = listeners.iterator();

        while (it.hasNext()) {

            try {
                ((AzureusCoreListener) it.next()).reportCurrentTask(op, currentTask);

            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }
    }

    protected void reportPercent(AzureusCoreOperation op, int percent) {
        if (op.getOperationType() == AzureusCoreOperation.OP_INITIALISATION) {

            PluginInitializer.fireEvent(PluginEvent.PEV_INITIALISATION_PROGRESS_PERCENT, new Integer(percent));
        }

        Iterator it = listeners.iterator();

        while (it.hasNext()) {

            try {
                ((AzureusCoreListener) it.next()).reportPercent(op, percent);

            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }
    }

    @Override
    public void addLifecycleListener(AzureusCoreLifecycleListener l) {
        lifecycle_listeners.add(l);
    }

    @Override
    public void removeLifecycleListener(AzureusCoreLifecycleListener l) {
        lifecycle_listeners.remove(l);
    }

    @Override
    public void addListener(AzureusCoreListener l) {
        listeners.add(l);
    }

    @Override
    public void removeListener(AzureusCoreListener l) {
        listeners.remove(l);
    }

    @Override
    public void addOperationListener(AzureusCoreOperationListener l) {
        operation_listeners.add(l);
    }

    @Override
    public void removeOperationListener(AzureusCoreOperationListener l) {
        operation_listeners.remove(l);
    }

    public static void addCoreRunningListener(AzureusCoreRunningListener l) {
        mon_coreRunningListeners.enter();
        try {
            if (AzureusCoreImpl.coreRunningListeners != null) {
                coreRunningListeners.add(l);

                return;
            }
        } finally {
            mon_coreRunningListeners.exit();
        }

        l.azureusCoreRunning(AzureusCoreImpl.getSingleton());
    }

    @Override
    public void addPowerManagementListener(PowerManagementListener listener) {
        power_listeners.add(listener);
    }

    @Override
    public void removePowerManagementListener(PowerManagementListener listener) {
        power_listeners.remove(listener);
    }
}
